package com.jamiexu.lua.interpreter;



import com.jamiexu.lua.interpreter.exception.LexerException;
import com.jamiexu.lua.interpreter.exception.ParseException;
import com.jamiexu.lua.interpreter.token.ASTToken;
import com.jamiexu.lua.interpreter.token.SymbolToken;
import com.jamiexu.lua.interpreter.token.Token;
import com.jamiexu.lua.interpreter.type.IntegerType;
import com.jamiexu.lua.interpreter.type.StringType;

import java.util.HashMap;
import java.util.Stack;

public class Interpreter {
    private Token currentToken;
    private Lexer lexer;
    private Stack<Token> tokenStack = new Stack<>();
    private HashMap<String, VarValue> hashMap_vars = new HashMap<>();


    public void eat(Token.TYPE type) throws LexerException, ParseException {
        if (this.currentToken.type == type) {
            this.currentToken = lexer.getNextToken();
            this.tokenStack.push(this.currentToken);
        } else {
            error("Parsing token character error");
        }
    }

    public String var() throws ParseException, LexerException {
        String result = this.currentToken.value.toString();
        Token token = this.currentToken;
        if (token.type == Token.TYPE.CHARATCTER) {
            eat(Token.TYPE.CHARATCTER);
        } else if (token.type == Token.TYPE.INTEGER) {
            eat(Token.TYPE.INTEGER);
        }
        if (result == null)
            error("Token exception");
        return result;
    }

    private void putVar(String varName, Class<?> clas, Object object) {
        this.hashMap_vars.put(varName, new VarValue(clas, object));
    }


    public void expre(String codes) throws LexerException, ParseException {
        this.lexer = new Lexer(null);
        this.currentToken = this.lexer.getNextToken();
        this.tokenStack.push(this.currentToken);
        while (this.currentToken.type != Token.TYPE.EOF) {
            if (this.currentToken.type == Token.TYPE.EQUAL) {
                eat(Token.TYPE.EQUAL);
                Token token = this.tokenStack.pop();
                Token.TYPE type = token.type;
                String var = token.value + "";
                String var_name = this.tokenStack.pop().value + "";
                if (var.contains("\""))
                    this.hashMap_vars.put(var_name, new VarValue(StringType.class, var.substring(1, var.length() - 1)));
                else if (type == Token.TYPE.INTEGER) {
                    eat(Token.TYPE.INTEGER);
                    this.hashMap_vars.put(var_name, new VarValue(IntegerType.class, var));
                } else {
                    if (this.hashMap_vars.containsKey(var_name))
                        this.hashMap_vars.put(var_name, this.hashMap_vars.get(var));
                    else
                        error("Var \"" + var_name + "\" not found");
                }
            }
//            else if (this.currentToken.value.equals("if")) {
//                eat(Token.TYPE.CHARATCTER);
//                String left = (String) this.currentToken.value;
//                eat(Token.TYPE.CHARATCTER);
//                String op = (String) this.currentToken.value;
//                eat(Token.TYPE.ALPHA);
//                String right = (String) this.currentToken.value;
//                parseIf(left, op, right);
//            }
            else if (this.currentToken.type == Token.TYPE.CHARATCTER) {
                parseMethod(this.currentToken.value + "");
            }
        }

    }


    private ASTToken parseNode(String code) {
        char[] chars = code.toCharArray();
        ASTToken rootToken = new ASTToken(null, "$root");
        ASTToken curToken = rootToken;
        ASTToken newToken = null;
        Stack<SymbolToken> stack = new Stack<>();
        SymbolToken topToken = null;
        out:
        for (int i = 0; i < chars.length; i++) {
            if (!stack.empty())
                topToken = stack.lastElement();
            char cur = chars[i];
            switch (cur) {
                case '(':
                    stack.push(new SymbolToken(i, '('));
                    newToken = new ASTToken(curToken, "(");
                    curToken = newToken;
                    break;
                case '{':
                    stack.push(new SymbolToken(i, '{'));
                    newToken = new ASTToken(curToken, "{");
                    curToken = newToken;
                    break;

                case ')':
                    if (topToken != null) {
                        if (topToken.getData() == '(')
                            stack.pop();
                        else {
                            error("Symbol \"" + topToken.getData() + "\" in column " + topToken.getIndex() + " not match");
                            break out;
                        }
                    }
                    break;
                case '}':
                    if (topToken != null) {
                        if (topToken.getData() == '{')
                            stack.pop();
                        else {
                            error("Symbol \"" + topToken.getData() + "\" in column " + topToken.getIndex() + " not match");
                            break out;
                        }
                    }
                    break;
                default:
                    curToken.getChilds().append(cur);
            }
        }
        if (!stack.empty()) {
            SymbolToken symbolToken = stack.lastElement();
            error("Symbol \"" + symbolToken.getData() + "\" in column " + symbolToken.getIndex() + " not match");
        }
        return rootToken;
    }

    private void parseIf(String left, String op, String right) throws LexerException, ParseException {
        eat(Token.TYPE.CHARATCTER);
//        if (!left.contains("\""))
//            if (this.hashMap_vars.containsKey(left))
//                System.out.println(this.hashMap_vars.get(left));
//            else
//                error("Var \"" + left + "\" not found");
//        switch (op) {
//            case "==":
//                return if ()
//                break;
//        }

    }


    private void parseMethod(String value) throws LexerException, ParseException {
        eat(Token.TYPE.CHARATCTER);
        String var = this.tokenStack.pop().value + "";
        if (value.equals("print")) {
            if (var.contains("\""))
                System.out.println(var.substring(1, var.length() - 1));
            else {
                if (this.hashMap_vars.containsKey(var))
                    System.out.println(this.hashMap_vars.get(var).value);
                else
                    error("Var \"" + var + "\"\" not found");
            }
        } else if (value.equals("typeof")) {
            if (var.contains("\""))
                System.out.println(StringType.class);
            else {
                if (this.hashMap_vars.containsKey(var))
                    System.out.println(this.hashMap_vars.get(var).type);
                else
                    error("Var \"" + var + "\" not found");
            }
        }
    }

    public void parse(String codes) throws LexerException, ParseException {
        parseNode(codes);
//        expre(codes);
    }

    private void error(String message) throws ParseException {
        throw new ParseException(message);
    }

}
